package cn.o.app.net;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.ParseException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.util.Log;
import cn.o.app.event.Listener;
import cn.o.app.json.JsonUtil;
import cn.o.app.queue.QueueItem;
import cn.o.app.reflect.OField;

@SuppressWarnings({ "serial", "unchecked" })
public class NetTask<REQUEST, RESPONSE> extends
		QueueItem<INetTask<REQUEST, RESPONSE>> implements
		INetTask<REQUEST, RESPONSE> {

	protected static final String DEBUG_URL = "debug.url";
	protected static final String DEBUG_REQUEST_METHOD = "debug.request.method";
	protected static final String DEBUG_PARAMS = "debug.params";
	protected static final String DEBUG_ERROR_CODE = "debug.error.code";
	protected static final String DEBUG_EXCEPTION = "debug.exception";
	protected static final String DEBUG_RESPONSE = "debug.response";
	protected static final String DEBUG_REQUEST_COOKIE = "debug.request.cookie";
	protected static final String DEBUG_RESPONSE_COOKIE = "debug.response.cookie";

	public static final String METHOD_GET = "GET";

	public static final String METHOD_POST = "POST";

	protected REQUEST mRequest;

	protected RESPONSE mResponse;

	protected String mUrl;

	protected String mRequestMethod;

	protected NetAsyncTask mTask;

	protected boolean mRequestConvertible;

	protected boolean mResponseConvertible = false;

	protected Object mResponseConverted;

	protected String mReferer;
	protected boolean mDebug;

	// 是否是将请求参数的基础数组转换为带逗号分隔符的字符串:ids=1,2,3&name=lounien
	protected boolean mSplitArrayParams;

	// 是否是Rest方式请求:http://www.xxx.cn/detail/{id}/{name}/
	protected boolean mRestUrl;

	// 是否是POST Params方式请求:id=1&name=lounien
	protected boolean mPostParams;

	// 是否是POST JSON方式请求:{"id":1,name:"lounien"}
	protected boolean mPostJson;

	// 是否缓存服务返回的Cookie
	protected boolean mCookieCacheable;

	// 请求时是否使用缓存的Cookie
	protected boolean mCookieCached;

	// 缓存Cookie的标识
	protected String mCookieCacheId;

	public NetTask() {
		super();
		this.mUrl = "";
		this.mRequestMethod = METHOD_GET;
		this.mReferer = "";
		this.mCookieCacheId = "";
		this.mSplitArrayParams = true;
	}

	public void addListener(NetTaskListener<REQUEST, RESPONSE> listener) {
		super.addListener(listener);
	}

	public boolean isSplitArrayParams() {
		return mSplitArrayParams;
	}

	public void setSplitArrayParams(boolean splitArrayParams) {
		if (mStarted || mStoped) {
			return;
		}
		mSplitArrayParams = splitArrayParams;
	}

	public boolean isRestUrl() {
		return mRestUrl;
	}

	public void setRestUrl(boolean restUrl) {
		if (mStarted || mStoped) {
			return;
		}
		mRestUrl = restUrl;
	}

	public boolean isPostParams() {
		return mPostParams;
	}

	public void setPostParams(boolean postParams) {
		if (mStarted || mStoped) {
			return;
		}
		mPostParams = postParams;
	}

	public boolean isPostJson() {
		return mPostJson;
	}

	public void setPostJson(boolean postJson) {
		if (mStarted || mStoped) {
			return;
		}
		mPostJson = postJson;
	}

	public boolean isDebug() {
		return mDebug;
	}

	public void setDebug(boolean debug) {
		mDebug = debug;
	}

	protected void debuging(String event, String message) {

	}

	public boolean isRequestConvertible() {
		return mRequestConvertible;
	}

	public void setRequestConvertible(boolean requestConvertible) {
		if (mStarted || mStoped) {
			return;
		}
		mRequestConvertible = requestConvertible;
	}

	public boolean isResponseConvertible() {
		return mResponseConvertible;
	}

	public void setResponseConvertible(boolean responseConvertible) {
		if (mStarted || mStoped) {
			return;
		}
		mResponseConvertible = responseConvertible;
	}

	protected REQUEST convertToRequest() {
		return null;
	}

	protected void errorCodeVerify(RESPONSE response) {
		if (mDebug) {
			debuging(DEBUG_ERROR_CODE, "errorCodeVerify");
		}
	}

	protected Object convertFromResponse(RESPONSE response) {
		return null;
	}

	public Object getResponseConverted() {
		return mResponseConverted;
	}

	@Override
	public String getUrl() {
		return this.mUrl;
	}

	@Override
	public void setUrl(String url) {
		if (this.mStarted || this.mStoped) {
			return;
		}
		this.mUrl = url;
	}

	@Override
	public String getRequestMethod() {
		return this.mRequestMethod;
	}

	@Override
	public void setRequestMethod(String requestMethod) {
		if (this.mStarted || this.mStoped) {
			return;
		}
		this.mRequestMethod = requestMethod;
	}

	@Override
	public REQUEST getRequest() {
		return this.mRequest;
	}

	@Override
	public void setRequest(REQUEST request) {
		if (this.mStarted || this.mStoped) {
			return;
		}
		this.mRequest = request;
	}

	@Override
	public RESPONSE getResponse() {
		return this.mResponse;
	}

	@Override
	public void setResponse(RESPONSE response) {
		if (this.mStoped) {
			return;
		}
		this.mResponse = response;
	}

	@Override
	public boolean start() {
		boolean result = super.start();
		if (result) {
			this.mTask = new NetAsyncTask();
			this.mTask.execute();
		}
		return result;
	}

	@Override
	public boolean stop() {
		boolean result = super.stop();
		if (result) {
			if (this.mResponse == null) {
				if (this.mTask != null) {
					this.mTask.cancel(true);
				}
			}
		}
		mContext = null;
		mRequest = null;
		mResponse = null;
		mResponseConverted = null;
		mTask = null;
		return result;
	}

	@Override
	public boolean isCookieCached() {
		return this.mCookieCached;
	}

	@Override
	public void setCookieCached(boolean cookieCached) {
		if (this.mStarted || this.mStoped) {
			return;
		}
		this.mCookieCached = cookieCached;
	}

	public static String convertToRestUrl(Object object, String url) {
		if (url == null) {
			return url;
		}
		if (object == null) {
			return url;
		}
		if (object instanceof Map) {
			for (Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) {
				Object key = entry.getKey();
				Object value = entry.getValue();
				if (key == null || value == null) {
					continue;
				}
				try {
					String restKey = "{" + key.toString() + "}";
					if (!url.contains(restKey)) {
						continue;
					}
					String restValue = URLEncoder.encode(
							JsonUtil.convert(value), HTTP.UTF_8);
					url = url.replace(restKey, restValue);
				} catch (Exception e) {

				}
			}
		} else {
			Class<?> objectClass = object.getClass();
			OField[] fields = OField.getFields(objectClass);
			for (int i = 0, size = fields.length; i < size; i++) {
				OField field = fields[i];
				try {
					Object value = field.get(object);
					if (value == null) {
						continue;
					}
					String restKey = "{" + field.getName() + "}";
					if (!url.contains(restKey)) {
						continue;
					}
					String restValue = URLEncoder.encode(
							JsonUtil.convert(value), HTTP.UTF_8);
					url = url.replace(restKey, restValue);
				} catch (Exception e) {

				}
			}
		}
		return url;
	}

	public static String convertToParameters(Object object,
			boolean splitArrayParams) {
		if (object == null) {
			return "";
		}
		boolean firstParam = true;
		StringBuilder sb = new StringBuilder();
		if (object instanceof Map) {
			for (Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) {
				Object key = entry.getKey();
				Object value = entry.getValue();
				if (key == null || value == null) {
					continue;
				}
				if (firstParam) {
					firstParam = false;
				} else {
					sb.append("&");
				}
				sb.append(key.toString());
				sb.append("=");
				try {
					if ((value instanceof List) && splitArrayParams) {
						sb.append(convertToSplitArrayParam((List<?>) value));
					} else {
						sb.append(URLEncoder.encode(JsonUtil.convert(value),
								HTTP.UTF_8));
					}
				} catch (Exception e) {

				}
			}
		} else {
			Class<?> objectClass = object.getClass();
			OField[] fields = OField.getFields(objectClass);
			for (int i = 0, size = fields.length; i < size; i++) {
				OField field = fields[i];
				try {
					Object value = field.get(object);
					if (value == null) {
						continue;
					}
					if (firstParam) {
						firstParam = false;
					} else {
						sb.append("&");
					}
					sb.append(field.getName());
					sb.append("=");
					if ((value instanceof List) && splitArrayParams) {
						sb.append(convertToSplitArrayParam((List<?>) value));
					} else {
						sb.append(URLEncoder.encode(JsonUtil.convert(value),
								HTTP.UTF_8));
					}
				} catch (Exception e) {

				}
			}
		}
		return sb.toString();
	}

	public static String convertToSplitArrayParam(List<?> value) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0, size = value.size(); i < size; i++) {
			try {
				if (i != 0) {
					sb.append(",");
				}
				sb.append(URLEncoder.encode(JsonUtil.convert(value.get(i)),
						HTTP.UTF_8));
			} catch (Exception e) {

			}
		}
		return sb.toString();
	}

	// 将请求对象转换为HttpClient识别的HttpEntity进行请求
	// postJson:JSON风格，postParams:参数风格
	public static HttpEntity convertToEntity(Object object, boolean postJson,
			boolean postParams, boolean splitArrayParams) {
		if (object == null) {
			return null;
		}
		if (postJson) {
			try {
				StringEntity entity = new StringEntity(JsonUtil.convert(object));
				entity.setContentEncoding(HTTP.UTF_8);
				entity.setContentType("application/json");
				return entity;
			} catch (Exception e) {
				return null;
			}
		} else if (postParams) {
			try {
				StringEntity entity = new StringEntity(convertToParameters(
						object, splitArrayParams));
				entity.setContentEncoding(HTTP.UTF_8);
				return entity;
			} catch (Exception e) {
				return null;
			}
		} else {
			MultipartEntity entity = new MultipartEntity(
					HttpMultipartMode.BROWSER_COMPATIBLE);
			if (object instanceof Map) {
				for (Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) {
					Object key = entry.getKey();
					Object value = entry.getValue();
					if (key == null || value == null) {
						continue;
					}
					if (value instanceof File) {
						entity.addPart(key.toString(), new FileBody(
								(File) value));
					} else {
						try {
							entity.addPart(
									key.toString(),
									new StringBody(value.toString(), Charset
											.forName(HTTP.UTF_8)));
						} catch (Exception e) {

						}
					}
				}
			} else {
				Class<?> objectClass = object.getClass();
				OField[] fields = OField.getFields(objectClass);
				for (int i = 0, size = fields.length; i < size; i++) {
					OField field = fields[i];
					try {
						Object value = field.get(object);
						if (value == null) {
							continue;
						}
						if (value instanceof File) {
							entity.addPart(field.getName(), new FileBody(
									(File) value));
						} else {
							entity.addPart(
									field.getName(),
									new StringBody(value.toString(), Charset
											.forName(HTTP.UTF_8)));
						}
					} catch (Exception e) {

					}
				}
			}
			return entity;
		}
	}

	class NetAsyncTask extends AsyncTask<String, Integer, Object> {

		protected Object doInBackground(String... paramters) {
			try {
				ConnectivityManager connMgr = (ConnectivityManager) mContext
						.getSystemService(Context.CONNECTIVITY_SERVICE);
				NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
				if (networkInfo == null || !networkInfo.isConnected()) {
					return new ConnectNotFoundException();
				}
				boolean isPost = METHOD_POST.equals(mRequestMethod);
				if (mRequestConvertible) {
					mRequest = convertToRequest();
				}
				String url = mUrl;
				String params = null;
				HttpGet httpGet = null;
				HttpPost httpPost = null;
				if (mRestUrl) {
					url = convertToRestUrl(mRequest, url);
				}
				
				Log.i("testurl", "url:" + url.toString());

				if (isPost) {
					httpPost = new HttpPost(url);
					httpPost.setEntity(convertToEntity(mRequest, mPostJson,
							mPostParams, mSplitArrayParams));
				} else {
					params = convertToParameters(mRequest, mSplitArrayParams);
					httpGet = new HttpGet(url + (url.contains("?") ? "&" : "?")
							+ params);
				}
				
				Log.i("testurl", "url+params:" + (url + (url.contains("?") ? "&" : "?")
						+ params));
				
				if (mDebug) {
					debuging(DEBUG_URL, url);
					debuging(DEBUG_REQUEST_METHOD, mRequestMethod);
					debuging(
							DEBUG_PARAMS,
							params == null ? convertToParameters(mRequest,
									mSplitArrayParams) : params);
				}
				if (mCookieCached) {
					String cookie;

					cookie = mCookieCached ? NetCookieCache.getCookie(mContext,
							new URL(url)) : (mCookieCacheId + "=" + UUID
							.randomUUID());

					(isPost ? httpPost : httpGet).setHeader("Cookie", cookie);
					if (mDebug) {
						debuging(DEBUG_REQUEST_COOKIE, cookie);
					}
				}
				(isPost ? httpPost : httpGet).setHeader("Refer", mReferer);
				(isPost ? httpPost : httpGet).setHeader("Cache-Control",
						"no-cache");
				HttpParams requestParams = (isPost ? httpPost : httpGet)
						.getParams();
				requestParams.setParameter(HTTP.CONTENT_ENCODING, HTTP.UTF_8);
				requestParams.setParameter(HTTP.CHARSET_PARAM, HTTP.UTF_8);
				requestParams.setParameter(HTTP.DEFAULT_PROTOCOL_CHARSET,
						HTTP.UTF_8);
				DefaultHttpClient client = new DefaultHttpClient();
				HttpParams clientParams = client.getParams();
				clientParams.setParameter(CoreConnectionPNames.SO_TIMEOUT,
						60000);
				clientParams.setParameter(
						CoreConnectionPNames.CONNECTION_TIMEOUT, 60000);
				HttpResponse httpResponse = client.execute(isPost ? httpPost
						: httpGet);
				if (mCookieCacheable) {
					List<Cookie> cookies = client.getCookieStore().getCookies();
					if (cookies != null) {
						for (Cookie cookie : cookies) {
							String cookieName = cookie.getName();
							if (cookieName.contains(mCookieCacheId)) {
								String cookieStr = cookieName + "="
										+ cookie.getValue();
								NetCookieCache.setCookie(mContext,
										new URL(url), cookieStr);
								if (mDebug) {
									debuging(DEBUG_RESPONSE_COOKIE, cookieStr);
								}
								break;
							}
						}
					}
				}
				String response = EntityUtils.toString(
						httpResponse.getEntity(), HTTP.UTF_8);
				
				Log.i("testurl", "respones:" + response);
				
				
				if (mDebug) {
					debuging(DEBUG_RESPONSE, response);
				}

				Class<RESPONSE> resJsonClass = (Class<RESPONSE>) ((ParameterizedType) (NetTask.this
						.getClass().getGenericSuperclass()))
						.getActualTypeArguments()[1];

				RESPONSE resJson = JsonUtil.convert(response, resJsonClass);

				errorCodeVerify(resJson);
				mResponse = resJson;
				
				Log.i("testurl", "mResponse.end:");

				if (mResponseConvertible) {
					mResponseConverted = convertFromResponse(mResponse);
				}

				return resJson;
			} catch (ParseException e) {
				// TODO Auto-generated catch block
				Log.i("testurl", "ParseException:" + e.toString());
				e.printStackTrace();
				return e;
			} catch (IOException e) {
				// TODO Auto-generated catch block
				Log.i("testurl", "IOException:" + e.toString());
				e.printStackTrace();
				return e;
			} catch (Exception e) {
				// TODO Auto-generated catch block
				Log.i("testurl", "Exception:" + e.toString());
				e.printStackTrace();
				return e;
			}

		}

		protected void onPostExecute(Object object) {
			List<Listener> listeners = getListeners();
			for (Listener listener : listeners) {
				if (listener instanceof INetTaskListener) {
					INetTaskListener<REQUEST, RESPONSE> targetListener = (INetTaskListener<REQUEST, RESPONSE>) listener;
					if (object instanceof Exception) {
						targetListener.onException(NetTask.this,
								(Exception) object);
					} else {
						targetListener.onComplete(NetTask.this,
								NetTask.this.getResponse());
					}
				}
			}
			stop();
		}
	}

	public static class ConnectNotFoundException extends Exception {

	}

	public static class CookieExpiredException extends Exception {

	}
}
